//
// Created by song on 16-11-18.
//

#ifndef C0COMPILER_IR_H
#define C0COMPILER_IR_H

#include <vector>
#include <string>
#include <sstream>
#include <map>
#include <iostream>
#include "../helper/Error.h"

using std::string;
using std::vector;
using std::map;
using std::stringstream;

enum IRType{
    ADD,
    SUB,
    MUL,
    DIV,
    JMP,
    JGE,
    JG,
    JNE,
    JE,
    JLE,
    JL,
    ASSIGN,
    READ,
    READCHAR,
    WRITESTR,
    WRITECHAR,
    WRITE,
    WRITELN,
    ARRAY, //(ARRAY n) alloc n int space in stack, (ARRAY n,_,_,)
    PUSHARR,
    NOOP,
    PUSHPARAM,
    POPPARAM,
    ERROR,
    CALL,
    RETURN,
    DEFREF, // get value from an address.
    ASSIGNARR,
    FUNBEGIN,
    FUNEND,

    SGE,
    SG,
    SNE,
    SE,
    SLE,
    SL,
};

class TmpVar{
public:
    enum Type{
        VALUE, LABEL, STRING, VAR
    };
    int id;
    Type type;
    int value;
    string content;

    TmpVar():
            id(-1),
            type(VALUE),
            value(-1),
            content("")
    {}

    string name(){
        stringstream r;
        if(type==VALUE){
            r << "_val" << id;
        }else if(type==LABEL){
            r << "_L" << id << "_" << content;
        }else if(type==STRING){
            r << "_str" << id;
        }else{
            r << "_tmp" << id;
            if(!content.empty()){
                r << '_' << content;
            }
        }
        return r.str();
    }

    string debugStr(){
        stringstream r;
        r<<id<<'\t';
        switch(type) {
            case VALUE:   r<<"VALUE "; break;
            case LABEL:   r<<"LABEL "; break;
            case STRING:  r<<"STRING"; break;
            case VAR:     r<<"VAR   "; break;
        }
        r<< '\t' << value << '\t' <<  content << '\t' << name();
        return r.str();
    }

    string toString(){
        stringstream r;
        if(type==VALUE){
            r << value;
        }else if(type==LABEL){
            r << name();
        }else if(type==STRING){
            r << name() << "[" << content << "]";
        }else{
            r << name();
        }
        return r.str();
    }

    static TmpVar* var(){
        TmpVar* t = new TmpVar();
        t->id = nextID;
        t->type = VAR;
        nextID++;
        varList.push_back(t);
        return t;
    }

    static TmpVar* var(string name){
        TmpVar* t = new TmpVar();
        t->id = nextID;
        t->type = VAR;
        t->content = name;
        nextID++;
        varList.push_back(t);
        return t;
    }

    static TmpVar* str(string str){
        TmpVar* t = new TmpVar();
        t->id = nextID;
        t->type = STRING;
        t->content = str;
        nextID++;
        varList.push_back(t);
        return t;
    }

    static TmpVar* label(string str){
        TmpVar* t = new TmpVar();
        t->id = nextID;
        t->type = LABEL;
        t->content = str;
        nextID++;
        varList.push_back(t);
        return t;
    }

    static TmpVar* num(int value){
        TmpVar* t = new TmpVar();
        t->id = nextID;
        t->type = VALUE;
        t->value = value;
        nextID++;
        varList.push_back(t);
        return t;
    }

    static vector<TmpVar*> varList;
private:
    static int nextID;

};

class IR {
public:
    int id;
    TmpVar* label;
    IRType op;
    TmpVar* result;
    TmpVar* left;
    TmpVar* right;

    IR():
            id(-1),
            label(NULL),
            op(NOOP),
            result(NULL),
            left(NULL),
            right(NULL)
    {}

    IR(int id, TmpVar* label, IRType op, TmpVar* left, TmpVar* right, TmpVar* result):
            id(id),
            label(label),
            op(op),
            result(result),
            left(left),
            right(right)
    {}

    string fmt(string s, long width){
        stringstream r;
        long len = s.size();
        if(width-len>1){
            r << string(width-len, ' ');
        }
        r << s;
        return r.str();
    }

    string fmt(string s, long width, bool b){
        stringstream r;
        r << s;
        long len = s.size();
        if(width-len>1){
            r << string(width-len, ' ');
        }
        return r.str();
    }

    string toString(){
        stringstream r;
        string l;
        if(label!=NULL){
            r << fmt(label->toString()+':', 24, true);
        }else{
            r << string(24, ' ');
        }
        r << fmt(irTypeStr[op], 12, true);
        if(left!=NULL){
            r << fmt(left->toString(), 20);
        }else{
            r << string(20, ' ');
        }
        r << ',';
        if(right!=NULL){
            r << fmt(right->toString(), 20);
        }else{
            r << string(20, ' ');
        }
        r << ',';
        if(result!=NULL){
            r << fmt(result->toString(), 20);
        }else{
            r << string(20, ' ');
        }
        r << ',';
        return r.str();
    }

    string toString(bool align){
        stringstream r;
        long len = irTypeStr[op].size();

        r << irTypeStr[op];
        if(align && 10-len>0){
            r << string(10-len, ' ');
        }else{
            r << ' ';
        }
        if(left!=NULL){
            r << left->toString();
        }
        r  << ", ";
        if(right!=NULL){
            r << right->toString();
        }
        r  << ", ";
        if(result!=NULL){
            r << result->toString();
        }
        if(label!=NULL){
            r << "#(" << label->toString() << ')';
        }
        return r.str();
    }

    string print(){
        stringstream r;
        string l;
        if(label!=NULL){
            r << label->toString() << ':' << std::endl;
        }
        long len = irTypeStr[op].size();

        r << "    " << irTypeStr[op];
        if(10-len>0){
            r << string(10-len, ' ');
        }
        if(left!=NULL){
            r << left->toString();
        }
        r  << ", ";
        if(right!=NULL){
            r << right->toString();
        }
        r  << ", ";
        if(result!=NULL){
            r << result->toString();
        }
        return r.str();
    }

    static IR* B(TmpVar* label, IRType t, TmpVar* left, TmpVar* right, TmpVar* result){
        IR* r = new IR(nextID, label, t, left, right, result);
        nextID++;
        if(!initialed){
            init();
            initialed = true;
        }
//        std::cout << r->toString() << std::endl;
        return r;
    }

    static std::map<IRType, string> irTypeStr;
    static int nextID;
    static bool initialed;
    static void init(){
        irTypeStr[ADD] = "ADD";
        irTypeStr[SUB] = "SUB";
        irTypeStr[MUL] = "MUL";
        irTypeStr[DIV] = "DIV";
        irTypeStr[JMP] = "JMP";
        irTypeStr[JGE] = "JGE";
        irTypeStr[JG] = "JG";
        irTypeStr[JNE] = "JNE";
        irTypeStr[JE] = "JE";
        irTypeStr[JLE] = "JLE";
        irTypeStr[JL] = "JL";
        irTypeStr[ASSIGN] = "ASSIGN";
        irTypeStr[READ] = "READ";
        irTypeStr[READCHAR] = "READCHAR";
        irTypeStr[WRITESTR] = "WRITESTR";
        irTypeStr[WRITECHAR] = "WRITECHAR";
        irTypeStr[WRITE] = "WRITE";
        irTypeStr[WRITELN] = "WRITELN";
        irTypeStr[ARRAY] = "ARRAY"; //(ARRAY n) alloc n int space in stack
        irTypeStr[PUSHARR] = "PUSHARR";
        irTypeStr[NOOP] = "NOOP";
        irTypeStr[PUSHPARAM] = "PUSHPARAM";
        irTypeStr[POPPARAM] = "POPPARAM";
        irTypeStr[ERROR] = "ERROR";
        irTypeStr[CALL] = "CALL";
        irTypeStr[RETURN] = "RETURN";
        irTypeStr[DEFREF] = "DEFREF"; // get value from an address.
        irTypeStr[ASSIGNARR] = "ASSIGNARR";
        irTypeStr[FUNBEGIN] = "FUNBEGIN";
        irTypeStr[FUNEND] = "FUNEND";
        irTypeStr[SGE] = "SGE";
        irTypeStr[SG] = "SG";
        irTypeStr[SNE] = "SNE";
        irTypeStr[SE] = "SE";
        irTypeStr[SLE] = "SLE";
        irTypeStr[SL] = "SL";
    }

};

class IRList{

    map<IR*,int> map2pos;
public:
    vector<IR*> clist;
    IRList(){

    }

    void append(IR* ir){
        clist.push_back(ir);
    }

    string toString(){
        stringstream r;
        vector<IR*>::const_iterator iter = clist.begin();
        for(;iter!=clist.end(); iter++){
            r << (*iter)->print() << std::endl;
        }
        return r.str();
    }

    vector<IR*>::const_iterator begin(){
        return clist.begin();
    }

    vector<IR *>::iterator end(){
        return clist.end();
    }

    IRList* getIRListBetween(IR *begin, IR *end) {
//        vector<IR*>::const_iterator last = clist.begin() + 101000;
        IRList* irList = new IRList();
        vector<IR*>::const_iterator iter;
        bool add=false;
        for(iter=clist.begin(); iter!=clist.end(); iter++){
            IR* ir = (*iter);
            if(ir==begin){
                add=true;
                irList->append(ir);
            }else if(ir==end){
                if(add){
                    add=false;
                    irList->append(ir);
                    return irList;
                }else{
                    Error::nextErrorDetail << "getIRListBetween hit end before hit start";
                    Error::internal(Error::Should_Not_Happen);
                }
            }else if(add){
                irList->append(ir);
            }
        }
        Error::nextErrorDetail << "getIRListBetween empty result";
        Error::internal(Error::Should_Not_Happen);
    }
};

#endif //C0COMPILER_IR_H
